package edu.usc.csci578.project;

import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import edu.usc.csci578.project.generated.specified.Cluster;
import edu.usc.csci578.project.generated.specified.Topic;
import edu.usc.csci578.project.util.parse.ArchSpecifiedParser;
import edu.usc.csci578.project.util.parse.XMLJaxbParser;

/**
 * Detects Redundant Components by the 'distance' between two Cluster's topic
 * proportions
 */
public class RedundantComponentDetector
{
	/**
	 * Default location of the smellArch_specified XML file
	 */
	private static final String DEFAULT_ARCH_SPECIFIED_XML = XMLJaxbParser.XML_HOME
			+ "oodt-filemgr_smellArch_specified.xml";
	/**
	 * Default threshold to use for computing which components are redundant
	 */
	private static final double DEFAULT_THRESHOLD = 0.35;

	/**
	 * Outputs a list of possibly redundant components to System.out
	 * 
	 * @param args
	 *            optional:
	 *            <ul>
	 *            <li>First parameter: path to smellArch_specified XML file</li>
	 *            <li>Second parameter: distance threshold to use</li>
	 *            </ul>
	 * @throws FileNotFoundException
	 *             if the smellArch_specified XML file is not found
	 */
	public static void main(final String[] args) throws FileNotFoundException
	{
		RedundantComponentDetector detector;
		if (args != null && args.length > 0)
			detector = new RedundantComponentDetector(args[0]);
		else
			detector = new RedundantComponentDetector();
		Set<RedundantComponentPair> redundantComponentPairs;
		if (args != null && args.length > 1)
			redundantComponentPairs = detector.getRedundantComponents(Double
					.parseDouble(args[1]));
		else
			redundantComponentPairs = detector.getRedundantComponents();
		RedundantComponentDetector.outputRedundantComponents(new PrintWriter(
				System.out, true), redundantComponentPairs);
		System.out.println("---");
		System.out.println(redundantComponentPairs.size() + " redundant pairs");
		System.out.println("---");
	}

	/**
	 * Outputs a list of redundant components to the specified PrintWriter
	 * 
	 * @param out
	 *            PrintWriter to output to
	 * @param redundantComponentPairs
	 *            pairs of redundant components to output
	 */
	public static void outputRedundantComponents(final PrintWriter out,
			final Set<RedundantComponentPair> redundantComponentPairs)
	{
		out.println("---LIST OF REDUNDANT COMPONENT PAIRS---");
		for (final RedundantComponentPair pair : redundantComponentPairs)
		{
			out.println("Pair with topic proportion distance of "
					+ pair.getDistance() + ":");
			out.println("  " + pair.getFirst().getName() + " &&");
			out.println("  " + pair.getSecond().getName());
		}
	}

	/**
	 * List of Clusters read in from the ArchSpecified XML file
	 */
	final private List<Cluster> clusters;

	/**
	 * Constructor for reading from the default smellArch_specified XML file
	 * 
	 * @throws FileNotFoundException
	 *             if the smellArch_specified XML file is not found
	 */
	public RedundantComponentDetector() throws FileNotFoundException
	{
		this(DEFAULT_ARCH_SPECIFIED_XML);
	}

	/**
	 * Constructor for reading from a given smellArch_specified XML file
	 * 
	 * @param xmlFilePath
	 *            path to the smellArch_specified XML file
	 * @throws FileNotFoundException
	 *             if the smellArch_specified XML file is not found
	 */
	public RedundantComponentDetector(final String xmlFilePath)
			throws FileNotFoundException
	{
		clusters = new ArchSpecifiedParser(xmlFilePath).getRootObject()
				.getCluster();
	}

	/**
	 * Determines the distance between two clusters using the Euclidean distance
	 * between the topic proportions of the two clusters
	 * 
	 * @param cluster1
	 *            first cluster to compare
	 * @param cluster2
	 *            second cluster to compare
	 * @return distance between the two clusters
	 */
	private double distance(final Cluster cluster1, final Cluster cluster2)
	{
		double sumSquaredDifference = 0;
		// Create a map for the topics in cluster1
		final Map<BigInteger, Double> topics1 = new HashMap<BigInteger, Double>();
		for (final Topic t1 : cluster1.getDocTopic().getTopic())
			topics1.put(t1.getId(),
					Double.parseDouble(t1.getContent().get(0).toString()));
		// Create a map for the topics in cluster2
		final Map<BigInteger, Double> topics2 = new HashMap<BigInteger, Double>();
		for (final Topic t2 : cluster2.getDocTopic().getTopic())
			topics2.put(t2.getId(),
					Double.parseDouble(t2.getContent().get(0).toString()));
		// Create a union of all of the topics by unique ID in both cluster1 and
		// cluster2
		final Set<Topic> topicUnion = new TreeSet<Topic>(
				new Comparator<Topic>()
				{
					@Override
					public int compare(final Topic t1, final Topic t2)
					{
						return t1.getId().compareTo(t2.getId());
					}
				});
		topicUnion.addAll(cluster1.getDocTopic().getTopic());
		topicUnion.addAll(cluster2.getDocTopic().getTopic());
		for (final Topic currentTopic : topicUnion)
		{
			final BigInteger currentId = currentTopic.getId();
			final double clusterValue1 = topics1.containsKey(currentId) ? topics1
					.get(currentId) : 0;
			final double clusterValue2 = topics2.containsKey(currentId) ? topics2
					.get(currentId) : 0;
			sumSquaredDifference += Math.pow(clusterValue1 - clusterValue2, 2);
		}
		return Math.sqrt(sumSquaredDifference);
	}

	/**
	 * Gets a list of redundant component pairs that have a topic distance under
	 * the default threshold
	 * 
	 * @return pairs of components that have a distance less than the default
	 *         threshold
	 */
	public Set<RedundantComponentPair> getRedundantComponents()
	{
		return getRedundantComponents(DEFAULT_THRESHOLD);
	}

	/**
	 * Gets a list of redundant component pairs that have a topic distance under
	 * the given threshold
	 * 
	 * @param threshold
	 *            distance threshold to use
	 * @return pairs of components that have a distance less than the given
	 *         threshold
	 */
	public Set<RedundantComponentPair> getRedundantComponents(
			final double threshold)
	{
		final Set<RedundantComponentPair> redundantComponentPairs = new TreeSet<RedundantComponentPair>();
		final List<Cluster> components = new ArrayList<Cluster>();
		for (final Cluster c : clusters)
			if (c.getType().equals("spec"))
				components.add(c);
		for (int h = 0; h < components.size(); h++)
			for (int i = h + 1; i < components.size(); i++)
			{
				final Cluster cluster1 = components.get(h);
				final Cluster cluster2 = components.get(i);
				final double distance = distance(cluster1, cluster2);
				if (distance < threshold)
					redundantComponentPairs.add(new RedundantComponentPair(
							cluster1, cluster2, distance));
			}
		return redundantComponentPairs;
	}
}
